﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using CH.Easy.DbProvider.Common;
using System.Reflection;
using CH.Easy.DbProvider.Entity;
using System.Data.Common;
using System.Collections.ObjectModel;
using CH.Easy.DbProvider.ExpressionExtend;
using System.Data;

namespace CH.Easy.DbProvider.Core
{
    public abstract class DbTableProvider : IQueryProvider
    {
        public string TableName { get; set; }
        public abstract DbProviderType DbProviderType { get; }
        public abstract DbConnection DbConnection { get; }
        public virtual IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new DbTable<TElement>(this, expression);
        }
        public virtual IQueryable CreateQuery(Expression expression)
        {
            try
            {
                return (IQueryable)Activator.CreateInstance(
                    typeof(DbTable<>).MakeGenericType(DbProviderHelper.GetElementType(expression.Type)),
                    new object[] { this, expression });
            }
            catch
            {
                throw new Exception();
            }
        }
        public virtual TResult Execute<TResult>(Expression expression)
        {
            return (TResult)DbProviderHelper.ChangeType(this.Execute(expression), typeof(TResult));
        }
        public virtual object Execute(Expression expression)
        {
            ExpressionSolution solution = new ExpressionSolution(this.DbProviderType);
            SolutionResult solutionResult = solution.Work(expression);
            expression = solutionResult.Expression;
            return this.GetType().GetMethod(solutionResult.ExecuteType.ToString()).Invoke(this, new object[] { solutionResult });
        }
        public virtual object ExecuteSQLCommand(string sql, ExecuteType executeType, params DbParameter[] parameter)
        {
            return this.ExecuteCommand(sql, CommandType.Text, executeType, parameter);
        }
        public virtual object ExecuteStoredProcedureCommand(string spName,ExecuteType executeType, params DbParameter[] parameter)
        {
            return this.ExecuteCommand(spName, CommandType.Text, executeType, parameter);
        }
        protected virtual string GetMappingName(Expression expression)
        {
            string name = string.Empty;
            if (expression.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression me = expression as MemberExpression;
                name = me.Member.Name;
            }
            else if (expression.NodeType == ExpressionType.Call)
            {
                MethodCallExpression mc = expression as MethodCallExpression;
                if (mc.Method.Name == "As")
                {
                    name = (mc.Arguments[1] as ConstantExpression).Value.ToString();
                }
                else 
                {
                    name = (mc.Arguments[0] as MemberExpression).Member.Name;
                }
            }

            return name;

        }
        protected virtual LambdaExpression MappingResultExpression(LambdaExpression resultExpression, DbDataReader dr)
        {
            Type t;
            Dictionary<string, int> map = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
            ParameterExpression row = Expression.Parameter(dr.GetType(), "row");

            for (int i = 0, n = dr.FieldCount; i < n; i++)
            {
                map.Add(dr.GetName(i), i);
            }

            if (resultExpression.Body.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression me = resultExpression.Body as MemberExpression;
                t = me.Expression.Type;
                resultExpression = Expression.Lambda(Expression.Call(null, typeof(DbProviderHelper).GetMethod("GetFieldValue").MakeGenericMethod(me.Type),Expression.Call(row, dr.GetType().GetMethod("GetValue"), Expression.Constant(0))), row);
            }

     

            else if(resultExpression.Body.NodeType == ExpressionType.New)
            {

                NewExpression ne = resultExpression.Body as NewExpression;
                t = ne.Type;
                PropertyInfo[] propertyInfos = t.GetProperties();
                ReadOnlyCollection<Expression> args = ne.Arguments;
                Expression[] e = new Expression[args.Count];
                for (int i = 0; i < args.Count; i++)
                {
                    int index = -1;
                    map.TryGetValue(GetMappingName(args[i]), out index);

                    e[i] = Expression.Call(null, typeof(DbProviderHelper).GetMethod("GetFieldValue").MakeGenericMethod(propertyInfos[i].PropertyType), Expression.Call(row, dr.GetType().GetMethod("GetValue"), Expression.Constant(index)));
                }

                NewExpression newExpression = Expression.New(t.GetConstructors().First(), e);
                resultExpression = Expression.Lambda(newExpression, row);

            }
            else if (resultExpression.Body.NodeType == ExpressionType.MemberInit)
            {
                
                MemberInitExpression mie = resultExpression.Body as MemberInitExpression;
                t = mie.Type;
                PropertyInfo[] propertyInfos = t.GetProperties();
                ReadOnlyCollection<MemberBinding> args = mie.Bindings;
                MemberBinding[] e = new MemberBinding[args.Count];
                for (int i = 0; i < args.Count; i++)
                {
                    int index = -1;
                    map.TryGetValue(GetMappingName((args[i] as MemberAssignment).Expression), out index);



                    MethodCallExpression mce = Expression.Call(null, typeof(DbProviderHelper).GetMethod("GetFieldValue").MakeGenericMethod(propertyInfos[i].PropertyType), Expression.Call(row, dr.GetType().GetMethod("GetValue"), Expression.Constant(index)));
                    e[i] = Expression.Bind(args[i].Member, mce);
                }

                MemberInitExpression memberInitExpression = Expression.MemberInit(mie.NewExpression, e);
                resultExpression = Expression.Lambda(memberInitExpression, row);

            }
            else if (resultExpression.Body.NodeType == ExpressionType.Parameter) 
            {
                ParameterExpression parameterExpression = resultExpression.Body as ParameterExpression;
                t = parameterExpression.Type;
                PropertyInfo[] propertyInfos = t.GetProperties();
                List<MemberBinding> e = new List<MemberBinding>();

                foreach (var item in map)
                {
                    PropertyInfo pi = propertyInfos.First(p => p.Name == item.Key);
                    if (pi != null) 
                    {
                        int index = item.Value;


                        MethodCallExpression mce = Expression.Call(null, typeof(DbProviderHelper).GetMethod("GetFieldValue").MakeGenericMethod(pi.PropertyType), Expression.Call(row, dr.GetType().GetMethod("GetValue"), Expression.Constant(index)));




                        e.Add(Expression.Bind(pi, mce));
                    
                    }
                }
                NewExpression newExpression = Expression.New(t.GetConstructors().First());
                MemberInitExpression memberInitExpression = Expression.MemberInit(newExpression, e);
                resultExpression = Expression.Lambda(memberInitExpression, row);
            
            }






            return resultExpression;
        }
        public abstract object ExecuteReader(SolutionResult solutionResult);
        public abstract object ExecuteNonQuery(SolutionResult solutionResult);
        public abstract object ExecuteScalar(SolutionResult solutionResult);
        public abstract object ExecuteCommand(string cmdText, CommandType commandType, ExecuteType executeType, params DbParameter[] parameter);
    }
}
